/**
* Space by Giraldo Rosales.
* Visit www.liquidgear.net for documentation and updates.
*
*
* Copyright (c) 2009 Nitrogen Design, Inc. All rights reserved.
* 
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
* 
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
**/

package lg.flash.elements {
	//Flash
	import flash.utils.Timer;
	import flash.events.Event;
	
	//PaperVision3D Classes
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.cameras.Camera3D;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.core.effects.view.ReflectionView;
	import org.papervision3d.core.clipping.FrustumClipping;
	
	//LG Classes
	import lg.flash.events.ElementEvent;
	import lg.flash.elements.VisualElement;
	import lg.flash.space.Paper;
	import lg.flash.space.Box;
	
	
	/**
	* Dispatched when a space starts rendering.
	* @eventType mx.events.ElementEvent.PLAY
	*/
	[Event(name="element_play", type="lg.flash.events.ElementEvent")]
	
	/**
	* Dispatched when a space stops rendering.
	* @eventType mx.events.ElementEvent.STOP
	*/
	[Event(name="element_stop", type="lg.flash.events.ElementEvent")]
	
	
	public class Space extends VisualElement {
		//Public Vars
		/** Reference to PaperVision3D Viewport3D object **/
		public var viewport:Viewport3D	= new Viewport3D();
		/** Reference to PaperVision3D Scene3D object **/
		public var scene:Scene3D		= new Scene3D();
		/** Reference to the Papervision3D Camera3D object. **/
		public var camera:Camera3D		= new Camera3D();
		/** Reference to the Papervision3D ReflectionView viewportReflection property. **/
		public var viewportReflection:Viewport3D	= new Viewport3D();
		
		/** @private **/
		private var _renderer:BasicRenderEngine		= new BasicRenderEngine();
		/** @private **/
		private var _reflection:ReflectionView;
		/* @private */
		private var _stopTimer:Timer				= new Timer(250, 1);
		/** 
		*	Constructs a new Space object. Camera coordinates are all set to 0.
			Camera zoom is set to 100 and far is set to 1500 by default.
		*	@param obj Object containing all properties to construct the class	
		**/
		public function Space(obj:Object) {
			super();
			holder();
			
			data.x					= 0;
			data.y					= 0;
			data.autoPlay			= true;
			data.autoSize			= false;
			data.interactive		= false;
			data.reflection			= false;
			data.reflectHeight		= 0;
			data.navigateDrag		= false;
			data.camera				= {};
			data.camera.x			= 0;
			data.camera.y			= 0;
			data.camera.z			= 0;
			data.camera.focus		= 100;
			data.camera.rotationX	= 0;
			data.camera.rotationY	= 0;
			data.camera.rotationZ	= 0;
			data.camera.zoom		= 2;
			data.camera.far			= 1000;
			
			//Set Attributes
			setAttributes(obj);
			
			//Renderer
			_renderer.clipping		= new FrustumClipping(FrustumClipping.ALL);
			
			//Resize element
			super.width		= data.width;
			super.height	= data.height;
			
			viewport		= new Viewport3D(data.width, data.height, data.autoSize, data.interactive);
			
			if(data.interactive) {
				viewport.containerSprite.buttonMode	= true;
			}
			
			addChild(viewport);
			
			if(data.reflection) {
				_reflection						= new ReflectionView(data.width, data.height, true, data.interactive);
				_reflection.viewport			= viewport;
				_reflection.scene				= scene;
				_reflection.surfaceHeight		= data.reflectHeight;
				_reflection.autoScaleToStage	= data.autoSize;
				viewportReflection				= _reflection.viewportReflection;
				camera							= _reflection.cameraAsCamera3D;
				addChild(_reflection);
			}
			
			//Set default view
			camera.x			= data.camera.x;
			camera.y			= data.camera.y;
			camera.x			= data.camera.z;
			camera.focus		= data.camera.focus;
			camera.rotationX	= data.camera.rotationX;
			camera.rotationY	= data.camera.rotationY;
			camera.rotationZ	= data.camera.rotationZ;
			camera.zoom			= data.camera.zoom;
			camera.far			= data.camera.far;
			camera.useCulling	= true;
			
			_stopTimer.addEventListener('complete', onDelayStop);
			
			isSetup = true;
			bind(ElementEvent.ADD, onAddedToStage);
		}
		
		/** Start rendering the 3D scene. **/
		public function play():void {
			if(!isPlaying) {
				bind('element_enter', onEnter);
			}
		}
		
		/** Stop rendering the 3D scene. **/
		public function stop():void {
			_stopTimer.start();
		}
		
		/* @private */
		private function onDelayStop(e:Event):void {
			if(isPlaying) {
				unbind('element_enter', onEnter);
			}
		}
		
		/** Determine if the scene is currently rendering. **/
		public function get isPlaying():Boolean {
			var playing:Boolean = hasEventListener('element_enter');
			return playing;
		}
		
		/** Start rendering 3D scene when added to stage. **/
		public function set autoPlay(value:Boolean):void {
			data.autoPlay	= value;
		}
		public function get autoPlay():Boolean {
			return data.autoPlay;
		}
		
		/** @private **/
		private function onAddedToStage(e:ElementEvent):void {
			unbind('element_add', onAddedToStage);
			
			if(scene.numChildren > 0 && data.autoPlay) {
				play();
			}
		}
		
		/** @private **/
		private function onEnter(e:ElementEvent=null):void {
			if(scene.numChildren > 0) {
				if(data.reflection) {
					_reflection.singleRender();
				} else {
					_renderer.renderScene(scene, camera, viewport);
				}
			}
		}
		
		/** Add a Paper object to the space. **/
		public function addPaper(paper:Paper):void {
			children[paper.id]	= paper;
			
			if(!paper.isLoaded) {
				paper.loaded(onLoadPaper);
			} else {
				if(paper && paper.plane) {
					var plane:Plane	= paper.plane as Plane;
					addPlane(plane, paper.id);
				}
			}
		}
		
		/** @private **/
		private function onLoadPaper(e:ElementEvent):void {
			var paper:Paper	= e.target as Paper;
			paper.unbind('element_loaded', onLoadPaper);
			
			if(paper && paper.plane) {
				var plane:Plane	= paper.plane as Plane;
				addPlane(plane, paper.id);
			}
		}
		
		private function addPlane(plane:Plane, id:String):void {
			scene.addChild(plane, id);
			onEnter();
		}
		
		/** Get a Paper object from the space.
		*	@param id The unique id of the Paper object. **/
		public function getPaper(id:String):Paper {
			if(children[id] is Paper) {
				var paper:Paper	= children[id];
				return paper;
			} else {
				return null;
			}
		}
		
		/** Remove a Paper object from the space. **/
		public function removePaper(paper:Paper):void {
			children[paper.id]	= null;
			
			scene.removeChild(paper.plane);
			
			paper.kill();
			paper = null;
		}
		
		/** Add a Box object to the space. **/
		public function addBox(box:Box):void {
			if(box.cube) {
				var cube:Cube	= box.cube as Cube;
				scene.addChild(cube, box.id);
				onEnter();
			}
		}
		
		/** Get a Box object from the space.
		*	@param id The unique id of the Box object. **/
		public function getBox(id:String):Box {
			if(children[id] is Box) {
				var box:Box	= children[id];
				return box;
			} else {
				return null;
			}
		}
		
		/** Remove a Box object from the space. **/
		public function removeBox(box:Box):void {
			children[box.id]	= null;
			
			scene.removeChild(box.cube);
			
			box.kill();
			box = null;
		}
		
		/** @private **/
		public function set navigateDrag(value:Boolean):void {
			data.navigateDrag	= value;
			
			if(data.navigateDrag) {
				mousedown(moveByDrag);
			} else {
				unbind(ElementEvent.MOUSEDOWN, moveByDrag);
			}
		}
		public function get navigateDrag():Boolean {
			return data.navigateDrag;
		}
		
		/** @private **/
		private function moveByDrag(e:ElementEvent):void {
			
		}
		
		/** Kill the object and clean from memory. **/
		public override function kill():void {
			unbind('element_add', onAddedToStage);
			unbind('element_enter', onEnter);
			//unbind(ElementEvent.MOUSEDOWN, moveByDrag);
			
			_renderer.destroy();
			
			for(var s:String in children) {
				removeChild(children[s]);
				children[s] = null;
			}
			super.kill();
		}
	}
}